#light
#types
#load "FsUnit.fs"

open FsUnit
open System
open System.Collections.Generic

type Key = seq<char>
type AnagramMap = Map<Key,list<string>>

module Anagram =
    let find inList =
        let key (s: string) =
            s
            |> Seq.map (fun c -> Char.ToUpperInvariant(c))
            |> Seq.orderBy (fun x -> x)
            |> Seq.to_list
            |> List.to_seq
        
        let rec collect (words: list<string>) (map: AnagramMap) =
            match words,map with
            | [],m    -> m
            | w::ws,m ->
                let k = key w
                let v =
                    match Map.tryfind k m with
                    | None   -> []
                    | Some x -> x
                if v |> List.exists (fun x -> x = w)
                    then collect ws m
                    else collect ws (Map.add k (w::v) m)
        collect inList (AnagramMap.Empty())
        |> Map.filter (fun k v -> Seq.length v > 1)
        |> Map.to_list
        |> List.map (fun (k,v) ->
            v 
            |> List.sort (fun s1 s2 -> s1.CompareTo(s2)))
            
spec "In an empty list, no anagrams are found."
    (Anagram.find [] |> should have 0 "sets of anagrams")

spec "In a list with a single word, no anagrams are found."
    (Anagram.find ["pet"] |> should have 0 "sets of anagrams")

spec "In a two-word list where the words are not anagrams, no anagrams are found."
    (Anagram.find ["pet";"top"] |> should have 0 "sets of anagrams")

spec "In a two-word list where the words are anagrams, one set of anagrams is found."
    (Anagram.find ["pot";"top"] |> should have 1 "set of anagrams")

spec "In a two-word list where the same word appears twice, no anagrams are found."
    (Anagram.find ["pot";"pot"] |> should have 0 "sets of anagrams")

spec "Lists words that are anagrams in alphabetical order."
    (Anagram.find ["pot";"top"] |> should equal [["pot";"top"]])

spec "Is not case-sensitive."
    (Anagram.find ["pot";"TOP"] |> should have 1 "set of anagrams")

spec "Does not count words as anagrams when one word contains the other plus one character."
    (Anagram.find ["pot";"tops"] |> should have 0 "sets of anagrams")
    
spec "Does not count words as anagrams when one word is like the other minus one character."
    (Anagram.find ["pot";"po"] |> should have 0 "sets of anagrams")

spec "In a three-word list where two anagrams are separated by a non-anagram, one set of anagrams is found."
    (Anagram.find ["pot";"pet";"top"] |> should have 1 "set of anagrams")

spec "In a three-word list where two anagrams are separated by a non-anagram, finds anagrams."
    (Anagram.find ["pot";"pet";"top"] |> should equal [["pot";"top"]])

spec "In a three-word list where all of the words are anagrams, three anagrams are found."
    (Anagram.find ["pot";"opt";"top"] |> should equal [["opt";"pot";"top"]])

spec "In a four-word list with two pairs of anagrams, two pairs of anagrams are found."
    (Anagram.find ["pot";"fresher";"top";"refresh"] |> should equal [["fresher";"refresh"];["pot";"top"]])

spec "In a dictionary-like list, the expected anagrams are found."
    (Anagram.find ["nomatch";"kinship";"enlist";"fresher";"boaster";"skins";"stink";
                   "boaters";"pinkish";"silent";"sort";"borates";"listen";"knits";
                   "rots";"refresh";"inlets";"sinks";"filler"]
     |> should equal [["boaster";"boaters";"borates"];
                      ["fresher";"refresh"];
                      ["enlist";"inlets";"listen";"silent"];
                      ["kinship";"pinkish"];
                      ["sinks";"skins"];
                      ["knits";"stink"];
                      ["rots";"sort"]])
                      
print_endline (results.Summarize())